/**
 * Copyright (c) 2022 Presto Labs Pte. Ltd.
 * Author: Ju
 */

#include "coin2/exchange/base/order/stats/system_info.h"

#include <sys/time.h>
#include <sys/times.h>
#include <unistd.h>

#include <cassert>
#include <fstream>

#include "coin2/base/time.h"

namespace {
static const uint64_t kOneSec = 1'000'000'000LL;
}

namespace coin2::exchange::base::order {
SystemInfo::SystemInfo(boost::asio::io_context *ioc,
                       std::shared_ptr<Scheduler> timer, uint32_t interval_s)
    : ioc_(ioc), timer_(timer), cpu_usage_(0) {
  timer_->AddPeriodicTask(std::bind(&SystemInfo::OnTimer, this),
                          interval_s * kOneSec, "system_info");
}

SystemInfo::~SystemInfo() {}

void SystemInfo::OnTimer() {
  cpu_usage_ = GetSelfCpuUsage(&cpu_time_data_);  // cpu usage
}

double SystemInfo::GetSelfCpuUsage(CpuTimeData *prev) {
  assert(prev != nullptr);

  CpuTimeData &last = *prev;
  struct tms cur;

  double percent = 0.0;
  clock_t now = times(&cur);
  const long ticks = sysconf(_SC_CLK_TCK);
  if (now > last.prev_ts && now < last.prev_ts + (ticks >> 1)) {
    LOG(WARNING) << "Too fast to call cpu statistics: prev tick "
                 << last.prev_ts << ", now " << now;
    return 0.0;
  }

  if (now <= last.prev_ts || cur.tms_stime < last.sys_time ||
      cur.tms_utime < last.user_time) {
    //  overflow detection. Just skip this value.
    percent = 0.0;
  } else {
    percent =
        (cur.tms_stime - last.sys_time) + (cur.tms_utime - last.user_time);
    percent /= static_cast<double>(now - last.prev_ts);
    percent *= 100;
  }

  if (percent < -100) {
    LOG(WARNING) << "overflow error! " << percent << ", prev: (ts "
                 << last.prev_ts << ", sys tick " << last.sys_time
                 << ", user tick " << last.user_time << ", cur: (ts " << now
                 << ", sys tick " << cur.tms_stime << ", user tick "
                 << cur.tms_utime;
    return 0;
  }

  last.prev_ts = now;
  last.sys_time = cur.tms_stime;
  last.user_time = cur.tms_utime;

  return percent;
}

bool GetSelfMemInfo(SelfMemInfo *info) {
  std::ifstream ifs("/proc/self/statm", std::ios_base::in);
  if (!ifs.is_open()) {
    return false;
  }
  ifs >> info->size >> info->resident >> info->share >> info->text >>
      info->lib >> info->data >> info->dt;
  return true;
}
}  // namespace coin2::exchange::base::order